package dd.lo.practice;

import dd.lo.util.ImageUtils;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.apache.commons.lang3.math.NumberUtils;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.ByteArrayInputStream;
import java.io.File;

public class MorphologyDisplay extends Application {

    public static void start(String[] args) {
        launch(args);
    }

    private static final int DEFAULT_WIDTH = 1200;
    private static final int DEFAULT_HEIGHT = 900;

    private static enum MorphologyEnum {
        ERODE("腐蚀"),
        DILATE("膨胀"),
        OPEN("开运算"), //erode(dilate(mat))，消除小物体，在纤细点处分离物体，会使靠近的小区块连接起来
        CLOSE("闭运算"), //dilate(erode(mat))，排除小型黑洞
        GRADIENT("形态学梯度"), //dilate - erode，用于保留物体的边缘轮廓
        TOP_HAT("顶帽"), //src - open，突出比原图轮廓周围的区域更明亮的区域
        BLACK_HAT("黑帽"), //close - src，突出比原图轮廓周围的区域更暗的区域，用于分离比邻近点暗一些的板块，有非常完美的轮廓
        ;

        private final String name;

        public String getName() {
            return name;
        }

        MorphologyEnum(String name) {
            this.name = name;
        }
    }

    private MorphologyEnum mode;

    private Mat srcImg;

    private SimpleStringProperty r; //形态学变化重复次数

    private SimpleStringProperty k; //过滤器的核心大小

    private Slider rSlider; //重复次数
    private Slider kSlider; //核心大小
    private ImageView imageView;

    private void imageProcess() {
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() {
                if (null == srcImg || srcImg.empty() || "0".equals(k.getValue())) return null;
                Mat displayImg = new Mat();
                int repeatTime = NumberUtils.toInt(r.getValue(), 1);
                int kernelSize = NumberUtils.toInt(k.getValue(), 0);
                if ((kernelSize & 1) == 0) kernelSize++; //形态学变换的内核边大小必须为正奇数
                Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_RECT, new Size(kernelSize, kernelSize));
                if (mode == MorphologyEnum.ERODE) {
                    Imgproc.erode(srcImg, displayImg, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.DILATE) {
                    Imgproc.dilate(srcImg, displayImg, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.OPEN) {
                    Imgproc.morphologyEx(srcImg, displayImg, Imgproc.MORPH_OPEN, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.CLOSE) {
                    Imgproc.morphologyEx(srcImg, displayImg, Imgproc.MORPH_CLOSE, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.GRADIENT) {
                    Imgproc.morphologyEx(srcImg, displayImg, Imgproc.MORPH_GRADIENT, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.TOP_HAT) {
                    Imgproc.morphologyEx(srcImg, displayImg, Imgproc.MORPH_TOPHAT, kernel, new Point(-1, -1), repeatTime);
                } else if (mode == MorphologyEnum.BLACK_HAT) {
                    Imgproc.morphologyEx(srcImg, displayImg, Imgproc.MORPH_BLACKHAT, kernel, new Point(-1, -1), repeatTime);
                }
                //显示
                ImageUtils.fitImgSize(displayImg, DEFAULT_WIDTH, DEFAULT_HEIGHT);
                MatOfByte buffer = new MatOfByte();
                Imgcodecs.imencode(".jpeg", displayImg, buffer);
                Platform.runLater(() -> imageView.setImage(new Image(new ByteArrayInputStream(buffer.toArray()))));
                return null;
            }
        };
        new Thread(task).start();
    }

    @Override
    public void start(Stage stage) throws Exception {
        Button imgChooserBtn = new Button("选择图片");
        imgChooserBtn.setOnMouseClicked(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("选择图片");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home") + "/Downloads")
            );
            fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("All Images", "*.*"));
            File imgFile = fileChooser.showOpenDialog(stage);
            if (null == imgFile) return;
            srcImg = Imgcodecs.imread(imgFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);
            if (srcImg.empty()) System.out.println("图片读取失败");
            imageProcess();
        });
        Text morphologyModeTip = new Text("形态学变换模式：");
        ChoiceBox<MorphologyEnum> morphologyMode = new ChoiceBox<>();
        morphologyMode.getItems().addAll(MorphologyEnum.ERODE, MorphologyEnum.DILATE, MorphologyEnum.OPEN, MorphologyEnum.CLOSE, MorphologyEnum.GRADIENT, MorphologyEnum.TOP_HAT, MorphologyEnum.BLACK_HAT);
        morphologyMode.setConverter(new StringConverter<MorphologyEnum>() {
            @Override
            public String toString(MorphologyEnum object) {
                return object.getName();
            }
            @Override
            public MorphologyEnum fromString(String string) {
                return null;
            }
        });
        morphologyMode.valueProperty().addListener((observable, oldValue, newValue) -> {
            mode = newValue;
            imageProcess();
        });
        morphologyMode.setValue(MorphologyEnum.ERODE); //初始化值
        //形态学变换重复次数
        Text rSliderTip = new Text("重复次数：");
        r = new SimpleStringProperty();
        Text rSliderValueText = new Text();
        rSliderValueText.textProperty().bind(r);
        rSlider = new Slider(1, 5, 1);
        rSlider.setPrefWidth(DEFAULT_WIDTH * 0.55);
        rSlider.setShowTickLabels(true);
        rSlider.setShowTickMarks(true);
        rSlider.setSnapToTicks(true);
        rSlider.setMajorTickUnit(5.0);
        rSlider.setMinorTickCount(4);
        rSlider.setBlockIncrement(1.0);
        rSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            r.set(Math.round(newValue.doubleValue()) + "");
            imageProcess();
        });
        r.set(Math.round(rSlider.getValue()) + ""); //初始化r值
        //核心大小的设定
        Text kSliderTip = new Text("核心大小：");
        k = new SimpleStringProperty();
        kSlider = new Slider(0, 51, 3);
        kSlider.setPrefWidth(DEFAULT_WIDTH * 0.75);
        kSlider.setShowTickLabels(true);
        kSlider.setShowTickMarks(true);
        kSlider.setSnapToTicks(true);
        kSlider.setMajorTickUnit(5.0);
        kSlider.setMinorTickCount(4);
        kSlider.setBlockIncrement(2.0);
        kSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
            k.set(Math.round(newValue.doubleValue()) + "");
            imageProcess();
            if (0 == newValue.intValue()) Platform.runLater(() -> kSlider.setValue(1.0));
        });
        k.set(Math.round(kSlider.getValue()) + ""); //初始化k值
        //关闭按钮
        Button exitBtn = new Button("关闭");
        exitBtn.setOnMouseClicked(e -> Platform.exit());
        VBox rows = new VBox(0);
        rows.setPadding(new Insets(10, 10, 0, 10));
        HBox row0 = new HBox(10);
        row0.setPadding(new Insets(5, 0, 5, 0));
        row0.getChildren().addAll(imgChooserBtn, morphologyModeTip, morphologyMode, exitBtn);
        HBox row1 = new HBox(10);
        row1.setPadding(new Insets(5, 0, 5, 0));
        row1.getChildren().addAll(rSliderTip, rSliderValueText, rSlider);
        HBox row2 = new HBox(10);
        row2.setPadding(new Insets(5, 0, 5, 0));
        row2.getChildren().addAll(kSliderTip, kSlider);
        rows.getChildren().addAll(row0, row1, row2);
        BorderPane root = new BorderPane();
        imageView = new ImageView(); //imageView放到一个pane的中间
        root.setCenter(imageView);
        root.setBottom(rows);
        Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT + 150);
        stage.setScene(scene);
        stage.setTitle("形态学变换演示");
        stage.setResizable(false);
        stage.show();
    }
}
